﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Factories;
using FolderData;

namespace Folder
{

    public static class FoldingHelper
    {

        #region Fold and Unfold

        public static bool IsFoldable(Map map, Vector3 foldingVec, Vector3 foldingPos)
        {
            return true;
        }

        private static bool isFoldCollision = false;
        private static bool OnCollision_foldableCheck(Geom g1, Geom g2, ContactList list)
        {
            isFoldCollision = true;
            return false;
        }

        public static void Fold(Map map, Vector3 foldingVec, Vector3 foldingPos)
        {
            Debug.Assert(map.IsFolded == false, "이미 접혀 있습니다.");
            map.IsFolded = true;

            map.foldingCameraPosition = map.Camera.Position;
            PhysicsSimulator simulator = new PhysicsSimulator(map.Simulator.Gravity);
            foldingPos += new Vector3(map.foldingCameraPosition - Camera.ScreenCenter, 0);

            Vector2 pointerPos = new Vector2(foldingPos.X, foldingPos.Y);
            Vector2 normal = new Vector2(foldingVec.X, foldingVec.Y); normal.Normalize();

            //점이 4개인 박스를 직접 만든다...
            Vertices box = new Vertices();
            float theta = (float)Math.Atan2(normal.Y, normal.X);
            {
                box.Add(new Vector2(-100, -100));
                box.Add(new Vector2(-100, 100));
                box.Add(new Vector2(100, 100));
                box.Add(new Vector2(100, -100));
                for (int i = 0; i < 4; i++)
                {
                    box[i] = new Vector2((float)Math.Sin(theta) * box[i].X + (float)Math.Cos(theta) * box[i].Y,
                                        (float)-Math.Cos(theta) * box[i].X + (float)Math.Sin(theta) * box[i].Y);
                    box[i] += pointerPos;
                }
            }

            //접기를 위한 변수
            Vector2 offset = pointerPos + 100 * normal;

            //기존 맵에 있던 물체들을 복사합니다.
            foreach (var s in map.Substances.Values)
            {
                if (s is Character || (s is Item && s.IsFoldable == true))
                {
                    if (Vertices.PointInPolygonAngle(s.Position, box))
                    {
                        s.Body.Position = Vector2.Reflect(s.Body.Position - offset, normal) + offset;
                        s.Body.LinearVelocity = Vector2.Reflect(s.Body.LinearVelocity, normal);
                    }
                    simulator.Add(s.Body);
                    simulator.Add(s.Geom);

                    //임시 패치. 고양이의 움직임을 바로잡습니다.
                    if (s == map.Player)
                    {
                        Character c =(s as Character);
                        if (Math.Abs(c.Body.LinearVelocity.Y) >= 10f) //10f 적당한 margin -_-;
                        {
                            //reset jump & fly state.
                            c.CurState = Character.State.JUMP;
                            c.CurJumpState = Character.JumpState.FLY;
                        }
                    }
                    continue;
                }
                if (s.IsVirtualGeom == true)
                    continue;
                if (s.IsFoldable == false || s is Item)
                {
                    simulator.Add(s.Body);
                    simulator.Add(s.Geom);
                    continue;
                }

                Vertices sVertices = new Vertices(s.Geom.WorldVertices);
                List<Vertices> remaining;
                List<Vertices> folded;
                PolyUnionError error = FoldingHelper.SubtractVertices(sVertices, box, out remaining, out folded);

                switch (error)
                {
                    case PolyUnionError.NoIntersections:
                        simulator.Add(s.Body);
                        simulator.Add(s.Geom);
                        break;

                    //box가 대상 geometry에 완전히 묻혀 있는 경우. 대상 geometry의 가운데를 비우는 것은 아직 처리되지 않았다.
                    case PolyUnionError.Poly2InsidePoly1:
                        simulator.Add(s.Body);
                        simulator.Add(s.Geom);
                        {
                            Vertices reflected = new Vertices();
                            foreach (Vector2 vec in box)
                            {
                                reflected.Add(Vector2.Reflect(vec - offset, new Vector2(foldingVec.X, foldingVec.Y)) + offset);
                            }
                            Terrain t = (Terrain)s.Clone();
                            t.Verts = reflected;
                            t.BindSimulator(simulator);
                        }
                        break;


                    case PolyUnionError.Poly1InsidePoly2:
                        {
                            Vertices reflected = new Vertices();
                            foreach (Vector2 vec in sVertices)
                            {
                                reflected.Add(Vector2.Reflect(vec - offset, new Vector2(foldingVec.X, foldingVec.Y)) + offset);
                            }
                            Terrain t = (Terrain)s.Clone();
                            t.Verts = reflected;
                            t.BindSimulator(simulator);
                        }
                        break;

                    case PolyUnionError.InfiniteLoop:
                        break;

                    case PolyUnionError.None:
                        //임시 패치. 몬스터 블럭은 접힘 구역 안에 들어가면 자동으로 사라집니다.
                        if (s is Terrain && (s as Terrain).Kind == Terrain.TerrainKind.MONSTER_BLOCK)
                            continue;

                        foreach (Vertices v in remaining)
                        {
                            if (v.Count < 3)
                                continue;
                            Terrain t = (Terrain)s.Clone();
                            t.Verts = v;
                            t.BindSimulator(simulator);
                        }
                        foreach (Vertices v in folded)
                        {
                            Vertices reflected = new Vertices();
                            foreach (Vector2 vec in v)
                            {
                                reflected.Add(Vector2.Reflect(vec - offset, new Vector2(foldingVec.X, foldingVec.Y)) + offset);
                            }
                            try
                            {
                                Terrain t = (Terrain)s.Clone();
                                t.Verts = reflected;
                                t.BindSimulator(simulator);
                            }
                            catch
                            {
                                continue;
                            }
                        }
                        break;
                }
            }


            Boundary.AddBoundaries(simulator);
            map.StateStack.Push(simulator);
        }

        public static void Unfold(Map map, Vector3 foldingVec, Vector3 foldingPos)
        {
            Debug.Assert(map.IsFolded == true, "접혀있지 않습니다.");
            map.IsFolded = false;

            Vector2 normal = new Vector2(foldingVec.X, foldingVec.Y); normal.Normalize();
            normal = Vector2.Negate(normal);
            foldingPos += new Vector3(map.foldingCameraPosition - Camera.ScreenCenter, 0);
            Vector2 pointerPos = new Vector2(foldingPos.X, foldingPos.Y) - 200 * normal;

            //점이 4개인 박스를 직접 만든다...
            Vertices box = new Vertices();
            float theta = (float)Math.Atan2(normal.Y, normal.X);
            {
                box.Add(new Vector2(-100, -100));
                box.Add(new Vector2(-100, 100));
                box.Add(new Vector2(100, 100));
                box.Add(new Vector2(100, -100));
                for (int i = 0; i < 4; i++)
                {
                    box[i] = new Vector2((float)Math.Sin(theta) * box[i].X + (float)Math.Cos(theta) * box[i].Y,
                                        (float)-Math.Cos(theta) * box[i].X + (float)Math.Sin(theta) * box[i].Y);
                    box[i] += pointerPos;
                }
            }
            Vector2 offset = pointerPos + 100 * normal;

            //플레이어 캐릭터를 복사합니다. 플레이어 캐릭터가 접힘선 뒤에 있는 경우, 반대쪽으로 복사합니다.
            //플레이어의 속도도 적당히 조절합니다.
            if (Vertices.PointInPolygonAngle(map.Player.Position, box))
            {
                map.Player.Body.Position = Vector2.Reflect(map.Player.Body.Position - offset, normal) + offset;
                map.Player.Body.LinearVelocity = Vector2.Reflect(map.Player.Body.LinearVelocity, normal);
            }

            map.StateStack.Pop();
        }

        #endregion

        #region Folding Helper Functions

        public static PolyUnionError SubtractVertices(Vertices polygon1, Vertices polygon2, out List<Vertices> remaining, out List<Vertices> folded)
        {
            // Make a copy of the polygons so that we dont modify the originals, and
            // force vertices to integer (pixel) values.
            Vertices poly1 = Vertices.Round(polygon1);
            Vertices poly2 = Vertices.Round(polygon2);

            // Find intersection points
            List<EdgeIntersectInfo> intersections = new List<EdgeIntersectInfo>();

            if (!Vertices.VerticesIntersect(poly1, poly2, ref intersections))
            {
                // No intersections found - polygons do not overlap.
                remaining = null; folded = null;
                foreach (Vector2 vec in poly1)
                    //만약 vec이 poly2 안에 없다면, Poly2InsidePoly1이 아니면
                    //NoIntersection이다.
                    if (!Vertices.PointInPolygonAngle(vec, poly2))
                    {
                        //이 두 경우를 나누기 위해, 거꾸로 검사를 진행한다.
                        foreach (Vector2 vec2 in poly2)
                            if (!Vertices.PointInPolygonAngle(vec2, poly1))
                                return PolyUnionError.NoIntersections;

                        return PolyUnionError.Poly2InsidePoly1;
                    }
                return PolyUnionError.Poly1InsidePoly2;
            }
            List<EdgeIntersectInfo> tmp = new List<EdgeIntersectInfo>();
            foreach (EdgeIntersectInfo info in intersections)
            {
                bool isSame = false;
                foreach (EdgeIntersectInfo info2 in tmp)
                    if (info.IntersectionPoint == info2.IntersectionPoint)
                    {
                        isSame = true;
                        break;
                    }
                if (!isSame)
                    tmp.Add(info);
            }
            intersections = tmp;

            Vertices intersectionVertices = new Vertices();
            
            //Collect data by edge1

            UpdateEdge(poly1, poly2, intersections);

            foreach (EdgeIntersectInfo intersec in intersections)
                if (!intersectionVertices.Contains(intersec.IntersectionPoint))
                    intersectionVertices.Add(intersec.IntersectionPoint);

            if (intersectionVertices.Count % 2 != 0)
            {
//                Debug.Assert(intersectionVertices.Count % 2 == 0, "교차점의 수가 짝수가 아닙니다. 확인 바람");
                remaining = null; folded = null;
                return PolyUnionError.InfiniteLoop;
            }

            int poly1startindex = -1;
            int poly2startindex = -1;

            //시작하는 vertax : 직전 vertax는 poly2안에 있고 현재 vertax는 poly2와 만나고 있음.
            //오류가 나는 경우 : 접하는 경우.
            //모든 vertax가 접할 때 : 오류를 낸다.
            for (int i = 0; i < poly1.Count; i++)
            {
                int start = (i) % poly1.Count;
                int end = (i + 1) % poly1.Count;
                int next = (i + 2) % poly1.Count;

                foreach (Vector2 intersect in intersectionVertices)
                {
                    if (poly1[end] == intersect && (Vertices.PointInPolygonAngle(poly1[start], poly2) || poly2.Contains(poly1[start])))
                    {
                        poly1startindex = end;
                        poly2startindex = poly2.IndexOf(poly1[end]);
                        break;
                    }
                }
                if (poly1startindex != -1)
                    break;
            }

            if (poly1startindex == -1)
            {
                remaining = null;
                folded = null;
                return PolyUnionError.Poly1InsidePoly2;
            }

            int poly1index = poly1startindex;
            int poly2index = poly2startindex;

            remaining = new List<Vertices>();

            for (int i = 0; i < poly1.Count; i++)
            {
                int start = (poly1index + i) % poly1.Count;
                int end = (poly1index + i + 1) % poly1.Count;

                if (intersectionVertices.Contains(poly1[start]))
                {
                    poly2index = poly2.IndexOf(poly1[start]);
                    Vertices vlist = new Vertices();

                    do
                    {
                        vlist.Add(poly1[end]);
                        ++i;
                        end = (end + 1) % poly1.Count;
                    }
                    while (!intersectionVertices.Contains(poly1[end]));
                    vlist.Add(poly1[end]);

                    while (!(poly1[end] == poly2[poly2index]))
                    {
                            vlist.Insert(0, poly2[poly2index]);
                            UpdateEdge(poly1, poly2, intersections);
                        poly2index = (poly2index + 1) % poly2.Count;
                    }
                    ++i;
                    remaining.Add(vlist);
                }
            }

            poly1index = poly1startindex;
            poly2index = poly2startindex;

            folded = new List<Vertices>();

            if (intersectionVertices.Count == 0)
                return PolyUnionError.None;

            Vertices vertices = new Vertices();

            bool isPoly1 = false;
            while(true)
            {
                if(isPoly1)
                {
                //    if (vertices.Contains(poly1[poly1index]))
                //        break;
                    vertices.Add(poly1[poly1index]);

                    poly1index = (poly1index + 1) % poly1.Count;
                    if(intersectionVertices.Contains( poly1[poly1index]))
                    {

                        isPoly1 = !isPoly1;
                        poly2index = poly2.IndexOf(poly1[poly1index]);
                        vertices.Add(poly1[poly1index]);
                    }
                }
                else
                {
                    //if(vertices.Contains(poly2[poly2index]))
                    //    break;
                    vertices.Add(poly2[poly2index]);

                    poly2index = (poly2index + 1) % poly2.Count;
                    if(intersectionVertices.Contains(poly2[poly2index]))
                    {
                        isPoly1 = !isPoly1;
                        poly1index = poly1.IndexOf(poly2[poly2index]);

                        if (vertices.Contains(poly2[poly2index]))
                            break;
                        else
                            vertices.Add(poly1[poly1index]);
                    }
                }
            }

            folded.Add(vertices);

            return PolyUnionError.None;
        }

        public static void UpdateEdge(Vertices poly1, Vertices poly2, List<EdgeIntersectInfo> intersections)
        {


            {
                List<EdgeIntersectInfo> info = new List<EdgeIntersectInfo>();

                foreach (EdgeIntersectInfo intersect in intersections)
                {
                    bool added = false;
                    for (int i = 0; i < info.Count; i++)
                    {
                        if (intersect.EdgeOne.EdgeStart == info[i].EdgeOne.EdgeStart &&
                            intersect.EdgeOne.EdgeEnd == info[i].EdgeOne.EdgeEnd)
                        {
                            //만약 겹친 점의 교차점이 새로운 점 뒤라면
                            if (Vector2.Dot(intersect.EdgeOne.EdgeEnd - intersect.EdgeOne.EdgeStart, info[i].IntersectionPoint - intersect.IntersectionPoint) >= 0)
                            {
                                //전에 추가한다.
                                info.Insert(info.IndexOf(info[i]), intersect);
                            }
                            //새로운 점 앞이라면, 그 뒤에도 같은 edgeone인지 확인하고, 맞다면 다음 점에 대해 확인한다.
                            else if (i < info.Count - 1 && info[i].EdgeOne == info[i + 1].EdgeOne)
                            {
                                continue;
                            }
                            else
                            {
                                info.Insert(info.IndexOf(info[i]) + 1, intersect);
                            }
                            added = true;
                            break;
                        }
                    }
                    if (added)
                        continue;
                    info.Add(intersect);
                }

                int index = 0;
                while (index < poly1.Count)
                {
                    int endindex = (index + 1) % poly1.Count;

                    for (int i = 0; i < info.Count; i++)
                    {
                        if (poly1[index] == info[i].EdgeOne.EdgeStart)
                        {
                            int thisindex = index;
                            while (poly1[thisindex] == info[i].EdgeOne.EdgeStart)
                            {
                                //만약 교차점이 이미 polygon안에 들어가 있다면,
                                //i는 증가시켜도 되지만, index는 증가시키면 안된다.
                                //index를 증가시키면 이 교차점이 polygon에 들어간 것으로 처리되기 때문에 문제가 생긴다.(다음 교차점이 polygon에 포함되지 않는 오류가 생긴다.)
                                if (!poly1.Contains(info[i].IntersectionPoint))
                                {
                                    ++index;
                                    poly1.Insert(index, info[i].IntersectionPoint);
                                }
                                ++i;

                                if (!(i < info.Count))
                                    break;
                            }
                            break;
                        }
                    }
                    ++index;
                }
            }

            {
                List<EdgeIntersectInfo> info = new List<EdgeIntersectInfo>();

                foreach (EdgeIntersectInfo intersect in intersections)
                {
                    bool added = false;
                    for (int i = 0; i < info.Count; i++)
                    {
                        if (intersect.EdgeTwo.EdgeStart == info[i].EdgeTwo.EdgeStart &&
                            intersect.EdgeTwo.EdgeEnd == info[i].EdgeTwo.EdgeEnd)
                        {
                            //만약 겹친 점의 교차점이 새로운 점 뒤라면
                            if (Vector2.Dot(intersect.EdgeTwo.EdgeEnd - intersect.EdgeTwo.EdgeStart, info[i].IntersectionPoint - intersect.IntersectionPoint) > 0)
                            {
                                //전에 추가한다.
                                info.Insert(info.IndexOf(info[i]), intersect);
                            }
                            //새로운 점 앞이라면, 그 뒤에도 같은 EdgeTwo인지 확인하고, 맞다면 다음 점에 대해 확인한다.
                            else if (i < info.Count - 1 && info[i].EdgeTwo == info[i + 1].EdgeTwo)
                            {
                                continue;
                            }
                            else
                            {
                                info.Insert(info.IndexOf(info[i]) + 1, intersect);
                            }
                            added = true;
                            break;
                        }
                    }
                    if (added)
                        continue;
                    info.Add(intersect);
                }

                int index = 0;
                while (index < poly2.Count)
                {
                    int endindex = (index + 1) % poly2.Count;

                    for (int i = 0; i < info.Count; i++)
                    {
                        if (poly2[index] == info[i].EdgeTwo.EdgeStart)
                        {
                            int thisindex = index;
                            while (poly2[thisindex] == info[i].EdgeTwo.EdgeStart)
                            {
                                if (!poly2.Contains(info[i].IntersectionPoint))
                                {
                                    ++index;
                                    poly2.Insert(index, info[i].IntersectionPoint);
                                }
                                ++i;

                                if (!(i < info.Count))
                                    break;
                            }
                            break;
                        }
                    }
                    ++index;
                }
            }
        }

        static public int[] calculateIndices(Vertices verts)
        {
            List<Vector3> pts = new List<Vector3>();
            verts.ForEach((vec) => pts.Add(new Vector3(vec.X, vec.Y, 0)));

            List<int> index = new List<int>();
            int[] Indices = new int[verts.Count];

            List<int> alls = new List<int>();
            List<int> convex = new List<int>();
            List<int> reflex = new List<int>();
            List<int> ear = new List<int>();

            calculateEar(pts, ref alls, ref convex, ref ear, ref reflex);

            while (!(convex.Count == 3 && reflex.Count == 0))
            {
                if (ear.Count == 0)
                    return index.ToArray();

                int e = ear[0];
                int before = alls[(alls.IndexOf(e) - 1 + alls.Count) % alls.Count];
                int after = alls[(alls.IndexOf(e) + 1 + alls.Count) % alls.Count];

                index.Add(after);
                index.Add(e);
                index.Add(before);

                alls.Remove(e);
                ear.Remove(e);
                convex.Remove(e);

                if (alls.Count == 0 && reflex.Count == 0 && convex.Count == 0)
                {
                    convex.Clear();
                    ear.Clear();

                    Indices = index.ToArray();
                    return Indices;
                }

                reCheckVertax(pts, alls, ear, convex, reflex, before);
                reCheckVertax(pts, alls, ear, convex, reflex, after);
            }

            index.Add(convex[2]);
            index.Add(convex[1]);
            index.Add(convex[0]);

            convex.Clear();
            ear.Clear();

            Indices = index.ToArray();
            return Indices;
        }

        static private void calculateEar(List<Vector3> pts, ref List<int> alls, ref List<int> convex, ref List<int> ear, ref List<int> reflex)
        {
            alls.Clear();
            convex.Clear();
            ear.Clear();
            reflex.Clear();

            for (int i = 0; i < pts.Count; i++)
                alls.Add(i);
            for (int i = 0; i < pts.Count; i++)
            {
                if (isConvex(pts, alls, i))
                    convex.Add(i);
                else
                    reflex.Add(i);
            }

            foreach (int e in convex)
            {
                if (isEar(pts, alls, e))
                    ear.Add(e);
            }
        }

        static private bool isConvex(List<Vector3> pts, List<int> alls,int e)
        {
            int before = alls[(alls.IndexOf(e) - 1 + alls.Count) % alls.Count];
            int after = alls[(alls.IndexOf(e) + 1 + alls.Count) % alls.Count];

            return !(Vector3.Cross(pts[e] - pts[before], pts[after] - pts[e]).Z >= 0);
        }

        static private void reCheckVertax(List<Vector3> pts, List<int> alls, List<int> ear, List<int> convex, List<int> reflex, int e)
        {
            ear.Remove(e);
            convex.Remove(e);
            reflex.Remove(e);
            if (!isConvex(pts, alls, e))
                reflex.Add(e);
            else
            {
                convex.Add(e);
                if (isEar(pts, alls, e))
                    ear.Add(e);
            }
            reflex.Sort();
            convex.Sort();
        }

        static private bool isEar(List<Vector3> pts, List<int> alls, int e)
        {
            int before = alls[(alls.IndexOf(e) - 1 + alls.Count) % alls.Count];
            int after = alls[(alls.IndexOf(e) + 1 + alls.Count) % alls.Count];

            for (int i = 0; i < pts.Count; i++)
            {
                if (i == before || i == e || i == after)
                    continue;
                if (isInside(pts[before], pts[e], pts[after], pts[i]))
                    return false;
            }

            return true;
        }

        private static bool isCrossing(Vector3 a1, Vector3 a2, Vector3 b1, Vector3 b2)
        {
            return (Vector3.Cross(b1 - a1, b1 - a2).Z * Vector3.Cross(b2 - a1, b2 - a2).Z) <= 0 &&
                (Vector3.Cross(a2 - a1, b1 - a2).Z * Vector3.Cross(a2 - a1, b2 - a2).Z) <= 0;
        }

        private static bool isInside(Vector3 a1, Vector3 a2, Vector3 a3, Vector3 p)
        {
            return (Vector3.Cross(p - a2, p - a1).Z <= 0 &&
                Vector3.Cross(p - a3, p - a2).Z <= 0 &&
                Vector3.Cross(p - a1, p - a3).Z <= 0);
        }

        #endregion
    }
}